package com.oxygen.util;

import java.io.InputStream;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.types.ObjectId;

import com.oxygen.components.ExtDBObject;
import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import com.mongodb.MapReduceCommand;
import com.mongodb.MapReduceOutput;
import com.mongodb.Mongo;
import com.mongodb.MongoOptions;
import com.mongodb.QueryBuilder;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;

/**
 * mongoDB 客户端
 * 
 * @author kevin
 * 
 */
public class MongoUtil {
	private static final Log log = LogFactory.getLog(MongoUtil.class);
	private static Object LOCK = new Object();
	private Properties properties;
	private static MongoUtil instance;
	private Mongo mongo;
	private DB db;

	private void init() {
		try {
			properties = new Properties();
			InputStream inStream = this.getClass().getClassLoader().getResourceAsStream("mongodb.properties");
			properties.load(inStream);
			MongoOptions options = new MongoOptions();
			options.autoConnectRetry = true;
			options.connectionsPerHost = 10;
			options.maxWaitTime = 5000;
			options.socketTimeout = 15000;
			options.connectTimeout = 15000;
			options.threadsAllowedToBlockForConnectionMultiplier = 20;
			String host = properties.getProperty("mongodb.host");
			int port = Integer.valueOf(properties.getProperty("mongodb.port"));
			String database = properties.getProperty("mongodb.database");

			// String host = "192.168.5.3";
			// int port = 27017;
			// String database = "test";
			mongo = new Mongo(new ServerAddress(host, port), options);
			this.db = mongo.getDB(database);
			// mongo = new
			// Mongo(DBMongoConfig.getHost(),DBMongoConfig.getPort());
			// or, to connect to a replica set, supply a seed list of members
			// Mongo m = new Mongo(Arrays.asList(new ServerAddress("localhost",
			// 27017),
			// new ServerAddress("localhost", 27018),
			// new ServerAddress("localhost", 27019)));

			// 注意Mongo已经实现了连接池，并且是线程安全的。
			// 大部分用户使用mongodb都在安全内网下，但如果将mongodb设为安全验证模式，就需要在客户端提供用户名和密码：
			// boolean auth = db.authenticate(myUserName, myPassword);

		} catch (Exception ex) {
			log.error("mongo init failed", ex);
		}
	}

	private MongoUtil() {
		init();
	}

	public static MongoUtil getInstance() {
		if (instance == null) {
			synchronized (LOCK) {
				if (instance == null) {
					instance = new MongoUtil();
				}
			}
		}
		return instance;
	}

	public DB getDB() {
		return db;
	}

	public DBCollection getCollection(String collname) {
		return db.getCollection(collname);
	}

	/**
	 * 获取所有数据库实例
	 */
	public List<String> getDataBase() {
		List<String> dbnames = mongo.getDatabaseNames();
		return dbnames;
	}

	/**
	 * 删除数据库
	 */
	public void dropDatabase(String dbName) {
		mongo.dropDatabase(dbName);
	}

	/**
	 * 查询所有表名
	 */
	public Set<String> getAllCollections() {
		Set<String> colls = db.getCollectionNames();
		return colls;
	}

	/**
	 * 删除表
	 * 
	 * @param collectionName
	 */
	public void dropCollection(String collectionName) {
		getCollection(collectionName).drop();
	}

	/**
	 * 创建表
	 * 
	 * @param collectionName
	 * @param doc
	 */
	public void createCollection(String collectionName, BasicDBObject doc) {
		db.createCollection(collectionName, doc);
	}

	/**
	 * 插入一行
	 * 
	 * @param collectionName
	 * @param row
	 */
	public void add(String collectionName, BasicDBObject row) {
		DBCollection coll = getCollection(collectionName);
		coll.insert(row);
		// 设定write concern，以便操作失败时得到提示
		coll.setWriteConcern(WriteConcern.SAFE);
	}

	/**
	 * 创建索引
	 * 
	 * @param collectionName
	 * @param row
	 */
	public void createIndex(String collectionName, BasicDBObject row) {
		getCollection(collectionName).createIndex(row);
	}

	/**
	 * 获取索引信息
	 * 
	 * @param collectionName
	 * @return
	 */
	public List<DBObject> getIndexInfo(String collectionName) {
		List<DBObject> list = getCollection(collectionName).getIndexInfo();
		return list;
	}

	/**
	 * 添加多条记录
	 * 
	 * @param collectionName
	 * @param docs
	 */
	public void addMultiData(String collectionName, List<DBObject> docs) {
		getCollection(collectionName).insert(docs);
		// 设定write concern，以便操作失败时得到提示
		getCollection(collectionName).setWriteConcern(WriteConcern.SAFE);
	}

	/**
	 * 查找第一条记录
	 * 
	 * @param collectionName
	 * @param o
	 * @return
	 */
	public DBObject findOne(String collectionName, DBObject o) {
		DBObject doc = getCollection(collectionName).findOne(o);
		return doc;
	}

	/**
	 * 查找第一条记录，可排序
	 * 
	 * @param collectionName
	 * @param o
	 * @return
	 */
	public DBObject findOne(String collectionName, ExtDBObject o) {
		List<DBObject> list = MongoUtil.getInstance().pageQuery(collectionName, 1, 1, o);
		if (list != null && !list.isEmpty()) {
			return list.get(0);
		}
		return null;
	}

	/**
	 * 查找对象（根据主键_id）
	 * 
	 * @param collection
	 * @param _id
	 */
	public DBObject findById(String collection, String id) {
		DBObject obj = new BasicDBObject();
		obj.put("_id", ObjectId.massageToObjectId(id));
		return getCollection(collection).findOne(obj);
	}

	/**
	 * 获取表中所有记录条数
	 * 
	 * @param collectionName
	 * @return
	 */
	public long count(String collectionName) {
		long count = getCollection(collectionName).getCount();
		return count;

	}

	/**
	 * 获取查询结果集的记录数
	 * 
	 * @param collectionName
	 * @param query
	 * @return
	 */
	public long getCount(String collectionName, DBObject query) {
		long count = getCollection(collectionName).count(query);
		return count;
	}

	/**
	 * 查询所有结果
	 * 
	 * @param collectionName
	 * @return
	 */
	public List<DBObject> getAllDocuments(String collectionName) {
		// DBCursor cursor =
		// coll.find(condition).addOption(Bytes.QUERYOPTION_NOTIMEOUT);//设置游标不要超时
		DBCursor cursor = getCollection(collectionName).find();
		List<DBObject> list = new ArrayList<DBObject>();
		try {
			while (cursor.hasNext()) {
				list.add(cursor.next());
			}
		} finally {
			cursor.close();
		}
		return list;
	}

	/**
	 * 按照条件查询
	 * 
	 * @param collectionName
	 * @param condition
	 * @return
	 */
	public List<DBObject> queryByCondition(String collectionName, ExtDBObject condition) {
		DBCursor cursor = getCollection(collectionName).find(condition);
		if (condition.getOrderName() != null) {
			cursor.sort(new BasicDBObject(condition.getOrderName(), condition.getSort()));
		}
		List<DBObject> list = new ArrayList<DBObject>();
		try {
			while (cursor.hasNext()) {
				list.add(cursor.next());
			}
		} finally {
			cursor.close();
		}
		return list;
	}

	/**
	 * IN查询 if i need to query name in (a,b); just use { name : { $in : ['a',
	 * 'b'] } } select * from things where name='a' or name='b'
	 * 
	 * @param coll
	 */
	public List<DBObject> queryIn(String collectionName, String filedName, BasicDBList values) {
		BasicDBObject in = new BasicDBObject("$in", values);
		DBCursor cursor = getCollection(collectionName).find(new BasicDBObject(filedName, in));
		List<DBObject> list = new ArrayList<DBObject>();
		try {
			while (cursor.hasNext()) {
				list.add(cursor.next());
			}
		} finally {
			cursor.close();
		}
		return list;
	}

	/**
	 * 或查询 select * from table where name = '12' or title = 'p'
	 * 
	 * @param coll
	 */
	public List<DBObject> queryOr(String collectionName, Map<String, Object> value) {
		QueryBuilder query = new QueryBuilder();
		String[] keys = new String[value.size()];
		keys = value.keySet().toArray(keys);
		BasicDBObject[] values = new BasicDBObject[keys.length];
		for (int i = 0; i < keys.length; i++) {
			values[i] = new BasicDBObject(keys[i], value.get(keys[i]));
		}
		query.or(values);
		DBCursor cursor = getCollection(collectionName).find(query.get());
		List<DBObject> list = new ArrayList<DBObject>();
		try {
			while (cursor.hasNext()) {
				list.add(cursor.next());
			}
		} finally {
			cursor.close();
		}
		return list;
	}

	/**
	 * 分页查询
	 * 
	 * @param collectionName
	 * @param page
	 * @param pageSize
	 * @param condition
	 * @return
	 */
	public List<DBObject> pageQuery(String collectionName, int page, int pageSize, ExtDBObject condition) {
		DBCursor cursor = getCollection(collectionName).find(condition).skip((page - 1) * pageSize).limit(pageSize);
		if (condition.getOrderName() != null) {
			cursor.sort(new BasicDBObject(condition.getOrderName(), condition.getSort()));
		}
		List<DBObject> list = new ArrayList<DBObject>();
		try {
			while (cursor.hasNext()) {
				list.add(cursor.next());
			}
		} finally {
			cursor.close();
		}
		return list;
	}

	/**
	 * 模糊查询
	 * 
	 * @param collectionName
	 * @param key
	 * @param content
	 * @return
	 */
	public List<DBObject> likeQuery(String collectionName, String key, String content) {
		Pattern john = Pattern.compile("^.*" + content + ".*$", Pattern.CASE_INSENSITIVE);
		BasicDBObject query = new BasicDBObject(key, john);
		DBCursor cursor = getCollection(collectionName).find(query);
		List<DBObject> list = new ArrayList<DBObject>();
		try {
			while (cursor.hasNext()) {
				list.add(cursor.next());
			}
		} finally {
			cursor.close();
		}
		return list;
	}

	/**
	 * 条件删除
	 * 
	 * @param collectionName
	 * @param key
	 * @param value
	 * @return
	 */
	public DBObject deleteAndFind(String collectionName, String key, String value) {
		BasicDBObject query = new BasicDBObject();
		if ("_id".equals(key)) {
			query.put(key, ObjectId.massageToObjectId(value));
		} else {
			query.put(key, value);
		}
		DBObject removeObj = getCollection(collectionName).findAndRemove(query);
		return removeObj;
	}

	/**
	 * 直接删除
	 * 
	 * @param collectionName
	 * @param key
	 * @param value
	 */
	public void delete(String collectionName, String key, String value) {
		BasicDBObject query = new BasicDBObject();
		if ("_id".equals(key)) {
			query.put(key, ObjectId.massageToObjectId(value));
		} else {
			query.put(key, value);
		}
		getCollection(collectionName).remove(query);
	}

	/**
	 * 更新
	 * 
	 * @param collectionName
	 * @param extDB
	 */
	public void update(String collectionName, ExtDBObject extDB) {
		BasicDBObject query = new BasicDBObject();
		if ("_id".equals(extDB.getKey())) {
			query.put(extDB.getKey(), ObjectId.massageToObjectId(extDB.getValue()));
		} else {
			query.put(extDB.getKey(), extDB.getValue());
		}
		DBObject obj = this.findOne(collectionName, query);
		obj.putAll(extDB.toMap());
		getCollection(collectionName).update(query, obj);
	}

	/**
	 * AND多条件查询,区间查询
	 */
	@Deprecated
	public void queryMulti() {
		BasicDBObject query = new BasicDBObject();
		// 查询j不等于3,k大于10的结果集
		query.put("j", new BasicDBObject("$ne", 3));
		query.put("k", new BasicDBObject("$gt", 10));
		DBCursor cursor = getCollection("wujintao").find(query);
		try {
			while (cursor.hasNext()) {
				System.out.println(cursor.next());
			}
		} finally {
			cursor.close();
		}
	}

	/**
	 * 区间查询 select * from table where i >50
	 */
	@Deprecated
	public void queryMulti2() {
		BasicDBObject query = new BasicDBObject();
		query = new BasicDBObject();
		query.put("i", new BasicDBObject("$gt", 50)); // e.g. find all where i >
		DBCursor cursor = getCollection("wujintao").find(query);
		try {
			while (cursor.hasNext()) {
				System.out.println(cursor.next());
			}
		} finally {
			cursor.close();
		}
	}

	/**
	 * 区间查询 select * from table where 20 < i <= 30 //比较符 //"$gt"： 大于
	 * //"$gte"：大于等于 //"$lt"： 小于 //"$lte"：小于等于 //"$in"： 包含
	 */
	@Deprecated
	public void queryMulti3() {
		BasicDBObject query = new BasicDBObject();
		query = new BasicDBObject();

		query.put("i", new BasicDBObject("$gt", 20).append("$lte", 30));
		DBCursor cursor = getCollection("wujintao").find(query);
		try {
			while (cursor.hasNext()) {
				System.out.println(cursor.next());
			}
		} finally {
			cursor.close();
		}
	}

	/**
	 * 组合in和and select * from test_Table where (a=5 or b=6) and (c=5 or d = 6)
	 */
	@Deprecated
	public void queryMulti4() {
		BasicDBObject query11 = new BasicDBObject();
		query11.put("a", 1);
		BasicDBObject query12 = new BasicDBObject();
		query12.put("b", 2);
		List<BasicDBObject> orQueryList1 = new ArrayList<BasicDBObject>();
		orQueryList1.add(query11);
		orQueryList1.add(query12);
		BasicDBObject orQuery1 = new BasicDBObject("$or", orQueryList1);

		BasicDBObject query21 = new BasicDBObject();
		query21.put("c", 5);
		BasicDBObject query22 = new BasicDBObject();
		query22.put("d", 6);
		List<BasicDBObject> orQueryList2 = new ArrayList<BasicDBObject>();
		orQueryList2.add(query21);
		orQueryList2.add(query22);
		BasicDBObject orQuery2 = new BasicDBObject("$or", orQueryList2);

		List<BasicDBObject> orQueryCombinationList = new ArrayList<BasicDBObject>();
		orQueryCombinationList.add(orQuery1);
		orQueryCombinationList.add(orQuery2);

		BasicDBObject finalQuery = new BasicDBObject("$and", orQueryCombinationList);
		DBCursor cursor = getCollection("wujintao").find(finalQuery);
		try {
			while (cursor.hasNext()) {
				System.out.println(cursor.next());
			}
		} finally {
			cursor.close();
		}
	}

	@Deprecated
	public void customQueryField() throws UnknownHostException {
		Mongo mongo = new Mongo("localhost", 27017);
		DB db = mongo.getDB("zhongsou_ad");
		BasicDBObjectBuilder bulder = new BasicDBObjectBuilder();
		bulder.add("times", 1);
		bulder.add("aid", 1);
		DBCursor cusor = db.getCollection("ad_union_ad_c_1").find(new BasicDBObject(), bulder.get());
		for (DBObject dbObject : cusor) {
			System.out.println(dbObject);
		}
	}

	@Deprecated
	public void mapReduce() throws UnknownHostException {
		Mongo mongo = new Mongo("localhost", 27017);
		DB db = mongo.getDB("zhongsou_ad");
		/***
		 * book1 = {name : "Understanding JAVA", pages : 100} book2 = {name :
		 * "Understanding JSON", pages : 200} db.books.save(book1)
		 * db.books.save(book2) book = {name : "Understanding XML", pages : 300}
		 * db.books.save(book) book = {name : "Understanding Web Services",
		 * pages : 400} db.books.save(book) book = {name :
		 * "Understanding Axis2", pages : 150} db.books.save(book)
		 * 
		 * var map = function() { var category; if ( this.pages >= 250 )
		 * category = 'Big Books'; else category = "Small Books"; emit(category,
		 * {name: this.name}); }; var reduce = function(key, values) { var sum =
		 * 0; values.forEach(function(doc) { sum += 1; }); return {books: sum};
		 * }; var count = db.books.mapReduce(map, reduce, {out:
		 * "book_results"});
		 */
		try {

			DBCollection books = db.getCollection("books");

			BasicDBObject book = new BasicDBObject();
			book.put("name", "Understanding JAVA");
			book.put("pages", 100);
			books.insert(book);

			book = new BasicDBObject();
			book.put("name", "Understanding JSON");
			book.put("pages", 200);
			books.insert(book);

			book = new BasicDBObject();
			book.put("name", "Understanding XML");
			book.put("pages", 300);
			books.insert(book);

			book = new BasicDBObject();
			book.put("name", "Understanding Web Services");
			book.put("pages", 400);
			books.insert(book);

			book = new BasicDBObject();
			book.put("name", "Understanding Axis2");
			book.put("pages", 150);
			books.insert(book);

			String map = "function() { " + "var category; " + "if ( this.pages >= 250 ) " + "category = 'Big Books'; " + "else " + "category = 'Small Books'; " + "emit(category, {name: this.name});}";

			String reduce = "function(key, values) { " + "var sum = 0; " + "values.forEach(function(doc) { " + "sum += 1; " + "}); " + "return {books: sum};} ";

			MapReduceCommand cmd = new MapReduceCommand(books, map, reduce, null, MapReduceCommand.OutputType.INLINE, null);

			MapReduceOutput out = books.mapReduce(cmd);

			for (DBObject o : out.results()) {
				System.out.println(o.toString());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Deprecated
	public void GroupByManyField() throws UnknownHostException {
		// 此方法没有运行成功
		Mongo mongo = new Mongo("localhost", 27017);
		DB db = mongo.getDB("libary");
		DBCollection books = db.getCollection("books");
		BasicDBObject groupKeys = new BasicDBObject();
		groupKeys.put("total", new BasicDBObject("$sum", "pages"));

		BasicDBObject condition = new BasicDBObject();
		condition.append("pages", new BasicDBObject().put("$gt", 0));

		String reduce = "function(key, values) { " + "var sum = 0; " + "values.forEach(function(doc) { " + "sum += 1; " + "}); " + "return {books: sum};} ";
		/**
		 * BasicDBList basicDBList =
		 * (BasicDBList)db.getCollection("mongodb中集合编码或者编码") .group(DBObject
		 * key, --分组字段，即group by的字段 DBObject cond, --查询中where条件 DBObject
		 * initial, --初始化各字段的值 String reduce, --每个分组都需要执行的Function String finial
		 * --终结Funciton对结果进行最终的处理
		 */
		DBObject obj = books.group(groupKeys, condition, new BasicDBObject(), reduce);
		System.out.println(obj);

		AggregationOutput ouput = books.aggregate(new BasicDBObject("$group", groupKeys));
		System.out.println(ouput.getCommandResult());
		System.out.println(books.find(new BasicDBObject("$group", groupKeys)));
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		BasicDBObject info = new BasicDBObject();
		// info.put("name", "Understanding JAVA");
		info.put("author", "jason");
		info.put("publish", "2014-10-10 18:22:50");

		ExtDBObject book = new ExtDBObject();
		book.setKey("name");
		book.setValue("Understanding JAVA");
		book.put("name", "think in java");
		book.put("pages", 10028);
		book.put("info", new Object[] { "11111", "2222", "33333" });
		MongoUtil.getInstance().add("table", book);

		DBRef ref = new DBRef(MongoUtil.getInstance().db, "table", book.getObjectId("_id"));
		ExtDBObject book1 = new ExtDBObject();
		book1.put("name", "java");
		book1.put("pages", 10028);
		book1.put("info", ref);
		MongoUtil.getInstance().add("table", book1);
		// MongoUtil.getInstance().createCollection("table", book);

		// MongoUtil.getInstance().update("table", book);
		// DBObject db = MongoUtil.getInstance().findOne("table", new
		// BasicDBObject("_id",
		// ObjectId.massageToObjectId("543914815efa9e91d869ae04")));
		// BasicDBList obj = (BasicDBList) db.get("info");

		// System.out.print(db);
		// MongoUtil.getInstance().getCount("table", inf
		// MongoUtil.getInstance().queryByCondition("table", info);
		// MongoUtil.getInstance().getAllDocuments("table");
		BasicDBList values = new BasicDBList();
		values.add("java");
		values.add("think in java");
		// MongoUtil.getInstance().queryIn("table", "name", values);
		Map<String, Object> map = new HashMap<String, Object>();
		// map.put("name", "java");
		// map.put("pages", 10028);
		ExtDBObject condetion = new ExtDBObject();
		condetion.setOrderName("pages");
		condetion.setSort(-1);
		condetion.putAll(map);

		// MongoUtil.getInstance().likeQuery("table", "name", "java");
		// MongoUtil.getInstance().queryOr("table", map);
		// MongoUtil.getInstance().pageQuery("table", 1, 4, condetion);
		// MongoUtil.getInstance().delete("table", "name", "java");

	}

}
